NodeJS - 理解 Buffer

简介

Buffer 是一个像 Array 的对象,主要用于操作字节,数组元素为 16 进制的两位数,即 0 到 255的数值,如果超出或者不足,小数等情况,会使用叠加,递减,省略小数部分的措施保证元素数值合法性,此外,采用 JavaScript 和 C++ 相结合的模式,将性能部分用 C++ 来实现,非性能相关的部分用 JavaScript 来实现

内存分配机制

Buffer 对象的内存分配不是在 V8 的堆内存中,而是在 Node 的 C++ 层面实现内存的申请,采用 slab 动态内存管理机制(先申请,后分配),以 4KB 作为界限来区分 Buffer 是大对象还是小对象,针对大对象,每次都 alloc 一个足够长的 SlowBuffer 对象(C++层)作为 slab 单元,针对小对象,则共用一个 slab,当不足以分配时(可分配小于 4KB),才会 alloc 一个新的 slab 内存进行再分配。需要注意的是当且仅当一个 slab 上面的所有小对象在作用域释放并都可以回收时,slab 的 8KB 才会被回收,此处会存在由于编码不当导致的内存泄漏,浪费问题

在 Buffer 中创建一个数组,需要注意以下规则:

  • Buffer 是内存拷贝,而不是内存共享
  • Buffer 占用内存被解释为一个数组,而不是字节数组。比如,new Uint32Array(new Buffer([1,2,3,4])) 创建了 4 个 Uint32Array,它的成员为 [1,2,3,4],而不是 [0x1020304] 或 [0x4030201]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 4KB 作为界限的由来:createPool 判断条件
Buffer.poolSize = 8 * 1024;
function allocate(size) {
if (size <= 0) {
return new FastBuffer();
}
if (size < (Buffer.poolSize >>> 1)) {
if (size > (poolSize - poolOffset))
createPool();
var b = new FastBuffer(allocPool, poolOffset, size);
poolOffset += size;
alignPool();
return b;
} else {
return createUnsafeBuffer(size);
}
}

四种 内存分配的 API

  • Buffer.from

  • Buffer.alloc

  • Buffer.allocUnSafe

  • Buffer.allocUnSafeSlow

支持与字符串相互转换

目前支持的字符串编码类型有:ASCII,UTF-8,UTF-16LE/UCS-2,Base64,Binary,Hex,可以用 isEncoding() 来判断是否支持编码

对于不支持的编码类型,可以通过 iconviconv-lite 两个模块来支持更多编码类型转换

转换成 Buffer:new Buffer(str,[encoding]) 和 buf.write(string,[offset],[length],[encoding])

转换成 String:buf.toString([encoding],[start],[end])

正确的拼接方式

用一个数组来存储接收到的所有 Buffer 片段并记录下所有片段的总长度,然后调用 Buffer.concat() 方法生成一个合并的 Buffer 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Buffer.concat = function concat(list, length) {
var i;
if (!Array.isArray(list))
throw kConcatErr;

if (list.length === 0)
return new FastBuffer();

if (length === undefined) {
length = 0;
for (i = 0; i < list.length; i++)
length += list[i].length;
} else {
length = length >>> 0;
}

var buffer = Buffer.allocUnsafe(length);
var pos = 0;
for (i = 0; i < list.length; i++) {
var buf = list[i];
if (!isUint8Array(buf))
throw kConcatErr;
_copy(buf, buffer, pos);
pos += buf.length;
}

// Note: `length` is always equal to `buffer.length` at this point
if (pos < length) {
// Zero-fill the remaining bytes if the specified `length` was more than
// the actual total length, i.e. if we have some remaining allocated bytes
// there were not initialized.
buffer.fill(0, pos, length);
}

return buffer;
};

Buffer 与性能

  • 网络传输用 Buffer 比直接传字符串要快很多

    通过预先转换静态内容为 Buffer 对象,可以有效地减少 CPU 的重复使用,节省服务器资源。在构建 Web 应用中,可以选择将页面中的动态内容和静态内容分离,静态内容部分可以通过预先转换为 Buffer 的方式,是性能得到提升。由于文件自身是二进制数据,所以在不需要改变内容的场景下,尽量只读取 Buffer,然后直接传输,不做额外的转换,避免损耗

  • 文件读取速度与 highWaterMark 有关

    这个值代表了每次读取的长度,对 Buffer 内存的分配和使用有一定影响,设置过小,可能导致系统调用的次数过多,在读取一个相同的大文件时,该值越大,读取的速度越快

如需转载,请注明出处